/**
 * (C) Copyright IBM Corp. 2010, 2015
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

package com.ibm.bi.dml.runtime.util;

import java.util.ArrayList;

/**
 * This native long long - double hashmap is specifically designed for
 * ctable operations which only require addvalue - extract semantics.
 * In contrast to a default hashmap the native representation allows us
 * to be more memory-efficient which is important for large maps in order
 * to keep data in the caches and prevent high-latency random memory access. 
 * 
 */
public class LongLongDoubleHashMap 
{
	private static final int INIT_CAPACITY = 8;
	private static final int RESIZE_FACTOR = 2;
	private static final float LOAD_FACTOR = 0.75f;

	private LLDoubleEntry[] data = null;
	private int size = -1;
	
	public LongLongDoubleHashMap()
	{
		data = new LLDoubleEntry[INIT_CAPACITY];
		size = 0;
	}

	public int size() {
		return size;
	}
	
	/**
	 * 
	 * @param key1
	 * @param key2
	 * @param value
	 */
	public void addValue(long key1, long key2, double value)
	{
		//compute entry index position
		int hash = hash(key1, key2);
		int ix = indexFor(hash, data.length);

		//find existing entry and add value
		for( LLDoubleEntry e = data[ix]; e!=null; e = e.next ) {
			if( e.key1==key1 && e.key2==key2 ) {
				e.value += value;
				return; //no need to append or resize
			}
		}
		
		//add non-existing entry (constant time)
		LLDoubleEntry enew = new LLDoubleEntry(key1, key2, value);
		enew.next = data[ix]; //colliding entries / null
		data[ix] = enew;
		size++;
		
		//resize if necessary
		if( size >= LOAD_FACTOR*data.length )
			resize();
	}
	
	/**
	 * 
	 * @return
	 */
	public ArrayList<LLDoubleEntry> extractValues()
	{
		ArrayList<LLDoubleEntry> ret = new ArrayList<LLDoubleEntry>();
		for( LLDoubleEntry e : data ) {
			if( e != null ) {
				while( e.next!=null ) {
					ret.add(e);
					e = e.next;
				}
				ret.add(e);	
			}
		}

		return ret;
	}
	
	/**
	 * 
	 */
	private void resize() {
		//check for integer overflow on resize
		if( data.length > Integer.MAX_VALUE/RESIZE_FACTOR )
			return;
		
		//resize data array and copy existing contents
		LLDoubleEntry[] olddata = data;
		data = new LLDoubleEntry[data.length*RESIZE_FACTOR];
		size = 0;
		
		//rehash all entries
		for( LLDoubleEntry e : olddata ) {
			if( e != null ) {
				while( e.next!=null ) {
					addValue(e.key1, e.key2, e.value);
					e = e.next;
				}
				addValue(e.key1, e.key2, e.value);	
			}
		}
	}
	
	/**
	 * 
	 * @param key1
	 * @param key2
	 * @return
	 */
	private static int hash(long key1, long key2) {
		//basic hash mixing of two longs hashes (w/o object creation)
		int h = (int)(key1 ^ (key1 >>> 32));
		h = h*31 + (int)(key2 ^ (key2 >>> 32));
		
		// This function ensures that hashCodes that differ only by
		// constant multiples at each bit position have a bounded
		// number of collisions (approximately 8 at default load factor).
		h ^= (h >>> 20) ^ (h >>> 12);
		return h ^ (h >>> 7) ^ (h >>> 4);
	}

	/**
	 * 
	 * @param h
	 * @param length
	 * @return
	 */
	private static int indexFor(int h, int length) {
		return h & (length-1);
	}
	
	/**
	 * 
	 */
	public class LLDoubleEntry {
		public long key1 = Long.MAX_VALUE;
		public long key2 = Long.MAX_VALUE;
		public double value = Double.MAX_VALUE;
		public LLDoubleEntry next = null;
		
		public LLDoubleEntry(long k1, long k2, double val) {
			key1 = k1;
			key2 = k2;
			value = val;
			next = null;
		}
	}
}
